/* proj3_starter_app - ECE 544 Project #3 Application template
 *
 * Copyright (c) 2012, 2013, 2014, 2015 Portland State University by Roy Kravitz.  All rights reserved.
 *
 * Author:		<your name>
 * Date:		<the date>
 * Revision:	2.1
 *
 * Revision History:
 * -----------------
 * 	21-Feb-2012		RK		Created this program from the xilkernel sample application produced by SDK
 * 	16-May-2013		RK		Updated the program for Nexys3 board and revised homework 2 assignment
 *	12-May-2014		RK		Minor changes to ease support for both Nexys 3 and Nexys 4.  No functional changes
 *  27-Feb-2015 	AV,AM	added the functionality for the threads and the wdt, basically the whole project 3
 *
 * 	Description:
 * 	------------
 * 	This program implements ECE 544 Project #3.  It is a Xilinx-based design that is meant to be a learning tool
 * 	for incorporating several of the key functions provided by Xilkernel.   The application sets up 4 independent
 * 	threads.  Thread 1 periodically reads the switches on the FPGA development board and sends an update to the LED thread.
 * 	Thread 2 responds to pushbutton press interrupts and sends an update to the LED thread whenever the state of
 * 	any of the pushbuttons on the FPGA development board change.   Thread 3 writes the new pushbutton and switch state to the
 * 	LEDs.   The three threads communicate via a message queue.  Thread 1 communicates to its interrupt handler through
 * 	the use of a semaphore.  The application also makes use of the Xilinx watchdog timer.
 * 	The 4th thread (the master thread) sets up the MicroBlaze, initializes the peripherals, creates the other threads and
 * 	the semaphore and then enters a main loop where it wakes up periodically to reset the WDT.  Status messages are sent
 * 	via STDOUT through the USB serial port which can be connected to a PC running a terminal emulator such as putty.
 *
 * <your additional comments>
 *
 */

// Includes
#include "xmk.h"
#include "os_config.h"
#include "config/config_param.h"
#include "sys/ksched.h"
#include "sys/init.h"
#include <pthread.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <semaphore.h>
#include <sys/intr.h>
#include <sys/timer.h>
#include <sys/types.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#include "xparameters.h"
#include "platform_config.h"
#include "platform.h"
#include "stdbool.h"
#include "xgpio.h"
#include "xwdttb.h"
#include "xtmrctr.h"
#include "xstatus.h"


// Declarations
// MOST OF THESE MAY NEED TO BE MODIFIED FOR YOUR SYSTEM'S xparameters.h
#define BTN_GPIO_DEVICEID		XPAR_GPIO_0_DEVICE_ID
#define SW_GPIO_DEVICEID		XPAR_GPIO_1_DEVICE_ID
#define LED_GPIO_DEVICEID		XPAR_GPIO_2_DEVICE_ID
#define INTC_DEVICEID			XPAR_INTC_0_DEVICE_ID
#define WDT_DEVICEID			XPAR_WDTTB_0_DEVICE_ID
#define TMRCTR0_DEVICEID		XPAR_TMRCTR_0_DEVICE_ID
#define TMRCTR1_DEVICEID		XPAR_TMRCTR_1_DEVICE_ID

#define TMRCTR0_INTR_NUM		XPAR_INTC_0_TMRCTR_0_VEC_ID
#define TMRCTR1_INTR_NUM		XPAR_INTC_0_TMRCTR_1_VEC_ID
#define BTN_GPIO_INTR_NUM		XPAR_AXI_INTC_0_AXI_GPIO_0_IP2INTC_IRPT_INTR
#define WDT_INTR_NUM			XPAR_AXI_INTC_0_AXI_TIMEBASE_WDT_0_WDT_INTERRUPT_INTR
// macro functions
#define MIN(a, b)  				( ((a) <= (b)) ? (a) : (b) )
#define MAX(a, b)  				( ((a) >= (b)) ? (a) : (b) )

// Peripheral instances
XGpio BTNInst, SWInst, LEDInst;
XTmrCtr TMRCTR1Inst;
XWdtTb WDTInst;

// Message passing variables
typedef struct					// LED message definition
{
	int msg_src;
	int msg_value;
} t_LED_message, *t_LED_messageptr;

const int led_msg_key = 1;		// message key for LED message queue
struct msqid_ds	led_msgstats;	// statistics from message queue

// Synchronization variables
sem_t btn_press_sema;			// semaphore between clock tick ISR and the clock main thread
volatile u32 btn_state;			// button state - shared between button handler and button thread

// Function declarations
void* master_thread(void *arg);
void* button_thread(void *arg);
void* switches_thread(void *arg);
void* leds_thread(void *arg);
void  button_handler(void);
void  wdt_handler(void);
XStatus init_peripherals(void);

/////////////////////////////////////////////////////////////////////////////////////////////////////////
// These are global variables which will be used by multiple functions
volatile int sys_state;
u32 msgID;
/////////////////////////////////////////////////////////////////////////////////////////////////////////


int main()
{

	XStatus sts;

	// initialize the platform and the peripherals
	init_platform();

	sts = init_peripherals();
	if (sts != XST_SUCCESS)
	{
		xil_printf("FATAL ERROR: Could not initialize the peripherals\n\r");
		xil_printf("Please power cycle or reset the system\n\r");
		return -1;
	}
	//xil_printf("Hello world\n");
	// check if WDT expired and caused the reset - if so, don't start
	if (XWdtTb_IsWdtExpired(&WDTInst))
	{
		xil_printf("*****************\r\n");
		// it's true, the WDT expired.
		//***** INSERT YOUR WDT RECOVERY CODE HERE ******//
	}

	// Initialize xilkernel
	xilkernel_init();
	// getting a queue ID here
	// this step only needs to actually be done once and since we are using only one queue
	// we need only one message ID
	msgID = msgget(led_msg_key, IPC_CREAT);
	// Create the master thread
	xmk_add_static_thread(master_thread, 0);
	// Start the kernel
	xilkernel_start();
	// Should never be reached
	cleanup_platform();

	return 0;
}


// The master thread
void* master_thread(void *arg)
{
	pthread_t button;
	pthread_t switches;
	pthread_t leds;

	pthread_attr_t attr;
	struct sched_param spar;

	int ret;

	xil_printf("----------------------------------------------------------------------------\r\n");
	xil_printf("ECE 544 Project 3 Starter Application \r\n");
	xil_printf("----------------------------------------------------------------------------\r\n");
	xil_printf("This Xilkernel based application reads the buttons and switches on the FPGA \r\n"
			"development board and displays them on the LEDs.  Even though the application is\r\n"
			"simple it uses several of the synchronization and interprocess communication\r\n"
			"capabilities offered in the Xilkernel\r\n\r\n"
			"To demonstrate, press any of the buttons and/or flip switches on the board.\r\n"
			"The current state of the buttons and switches should be displayed on the LEDs\r\n");
	xil_printf("----------------------------------------------------------------------------\r\n\r\n\r\n");;

	xil_printf("MASTER: Master Thread Starting\r\n");

	// set the priority of all but the master thread to 1.  The master thread runs at priority 0
	// because it is responsible for tickling the WDT.
	pthread_attr_init (&attr);
	spar.sched_priority = 1;
	pthread_attr_setschedparam(&attr, &spar);

	// create the threads
	ret = pthread_create (&button, &attr, (void*) button_thread, NULL);
	if (ret != 0)
	{
		xil_printf("ERROR (%d) IN MASTER THREAD: could not launch %s\r\n", ret, "button thread");
		xil_printf("FATAL ERROR: Master Thread Terminating\r\n");
		return (void*) -3;
	}
	ret = pthread_create (&switches, &attr, (void*) switches_thread, NULL);
	if (ret != 0)
	{
		xil_printf("ERROR (%d) IN MASTER THREAD: could not launch %s\r\n", ret, "switches thread");
		xil_printf("FATAL ERROR: Master Thread Terminating\r\n");
		return (void*) -3;
	}
	ret = pthread_create (&leds, &attr, (void*) leds_thread, NULL);
	if (ret != 0)
	{
		xil_printf("ERROR (%d) IN MASTER THREAD: could not launch %s\r\n", ret, "switches thread");
		xil_printf("FATAL ERROR: Master Thread Terminating\r\n");
		return (void*) -3;
	}

	// initialize the button press semaphore
	ret = sem_init (&btn_press_sema, 0, 0);
	if (ret != 0)
	{
		xil_printf("ERROR (%d) IN MASTER THREAD: could not initialize %s\r\n", errno, "button press semaphore");
		xil_printf("FATAL ERROR: Master Thread Terminating\r\n");
		return (void*) -3;
	}
	else
	{
		xil_printf ("MASTER: Button press semaphore has been initialized\n\r");
	}

	// Register the interrupt handlers
	ret = register_int_handler(WDT_INTR_NUM, (void*) wdt_handler, NULL);
	if (ret != XST_SUCCESS)
	{
		return (void*) -4;
	}
	ret = register_int_handler(BTN_GPIO_INTR_NUM, (void*) button_handler, NULL);
	xil_printf("*********** %d\r\n",ret);
	if (ret != XST_SUCCESS)
	{
		return (void*) -4;
	}

	// Enable interrupts and start the WDT...we're off to the races
	// Enabling the global interrupts, this would allow us to read all threads
	XGpio_InterruptGlobalEnable(&BTNInst);
	// enabling the interrupt for the button GPIO
	XGpio_InterruptEnable(&BTNInst, 1);
	// enabling reading of the button GPIO interrupt in the intc
	enable_interrupt(BTN_GPIO_INTR_NUM);
	// enabling reading the watchdog timer interrupt
	enable_interrupt(WDT_INTR_NUM);
	xil_printf("MASTER: Interrupts have been enabled\r\n");

	XWdtTb_Start(&WDTInst);
	xil_printf("MASTER: Watchdog timer has been started\r\n");

	// master thread main loop
	while(1)
	{
		// putting the master thread to sleep for sometime
		sleep(350);
	}

	return NULL;
}

/**
 * The button thread implements the functionality of waiting for the semaphore
 * which basically means that the thread will go to sleep till it sees an interrupt
 * and the button handler wakes the thread up. Apart from that the thread is responsible for
 * making sense of the button state (which it gets from the button handler) and putting the
 * appropriate message on the message queue.
 */
void* button_thread(void *arg)
{
	// declaring local variables to be used by the button thread
	int led_val;
	t_LED_message btn_msg;
	u32 old_btn_state=0;
	// to make the thread run continuously
	while(1){
		// waiting for the semaphore
		sem_wait(&btn_press_sema);
		// checking the button state and assigning the leds to be lit up accordingly
		if(btn_state!=0 && btn_state != old_btn_state){
			if(btn_state == 1){
				led_val = 0x100;
			}
			else if(btn_state == 2){
				led_val = 0x200;
			}
			else if(btn_state == 4){
				led_val = 0x400;
			}
			else if(btn_state == 8){
				led_val = 0x800;
			}
			else if(btn_state == 16){
				led_val = 0x1000;
			}
		}
		// if there is a change in the button state then we put it on the queue
		// a change is expected only when a button is pressed
		if(btn_state != old_btn_state){
			// putting a message on the queue
			btn_msg.msg_src = 1;
			// the actual message would be the message value
			btn_msg.msg_value = led_val;
			// sending the mesaage on the queue
			// in case if the message is not sent what kind of an error it is will be
			// displayed on the terminal
			if(msgsnd(msgID, &btn_msg, 8, 0)==-1){
				xil_printf("lets see the error in buttons: %s\r\n",strerror(errno));
			}
			old_btn_state = btn_state;
		}
		sleep(350);
	}
	return NULL;
}

/**
 * The switches thread takes care of the switches. the switches are not checked using an
 * interrupt and a semaphore. the thread will check the switches and then then add the
 * values for the leds to the message queue.
 */
void* switches_thread(void *arg)
{
	// these local variables will be used by the switches thread
	int switches, old_sw_val=0;
	t_LED_message sw_msg;

	// keep the thread running
	while(1){
		// reading in the values of the switches from the GPIO
		switches = XGpio_DiscreteRead(&SWInst, 1);
		// if the leftmost switch is high then we are forcing a system crash
		if((switches&0x8000) >= 1){
			xil_printf("crashing the system\r\n");
			sys_state = 1;							// force crash state
		}
		// usual operation
		else{
			sys_state = 0;							// No force crash
			if(switches!=old_sw_val){
				// adding the value of the switches to the message queue
				sw_msg.msg_src = 2;
				// the lower 8 leds are the only ones being used to we have them here
				sw_msg.msg_value = switches&0xff;
				// sending the message and catching the error (if any)
				if(msgsnd(msgID, &sw_msg, 8, 0)==-1){
					xil_printf("lets see the error number in switches: %s\r\n",strerror(errno));
				}
				// saving the switch value to the old switch value
				old_sw_val = switches;
			}
		}
		sleep(350);
	}
	return NULL;
}

/**
 * The led thread reads the message queue and lights up the required leds.
 * while this is a standard functionality reading the queue correctly is imperative
 */
void* leds_thread(void *arg)
{
	// local variables to be used by the led thread
	t_LED_message led_msg;
	int led_val, but_val, swi_val;
	// keep the thread running
	while(1){
		// receiving the message here from the message queue
		msgrcv(msgID, &led_msg, 8, 1, 0);
		// fetching the value from the message retrieved from the queue
		led_val = led_msg.msg_value;
		// for the lower 8 leds we have the switches
		if(led_val<= 128){
			swi_val = led_val;
		}
		// the upper leds have the buttons etc
		else{
			but_val = led_val;
		}
		// writing out to the leds
		XGpio_DiscreteWrite(&LEDInst, 1, ((swi_val)|(but_val)));
		sleep(350);
	}
	return NULL;
}


/**
 * this is the initialize peripherals function. here we initialize all the peripherals
 * that we are going to use.
 */
XStatus init_peripherals(void)
{

	// doing as a precaution
	cleanup_platform();
	init_platform();

	xil_printf("init peripherals\r\n");
	XStatus status;
	// initializing the button GPIO
	status = XGpio_Initialize(&BTNInst, BTN_GPIO_DEVICEID);
	if(status != XST_SUCCESS){
		xil_printf("btn gpio initialization failed");
		return XST_FAILURE;
	}

	// initialize the switches GPIO
	status = XGpio_Initialize(&SWInst, SW_GPIO_DEVICEID);
	if(status != XST_SUCCESS){
		xil_printf("sw gpio initialization failed");
		return XST_FAILURE;
	}

	// initialize the LED GPIO
	status = XGpio_Initialize(&LEDInst, LED_GPIO_DEVICEID);
	if(status != XST_SUCCESS){
		xil_printf("led gpio initialization failed");
		return XST_FAILURE;
	}

	// setting the direction of the LED GPIO
	XGpio_SetDataDirection(&LEDInst, 1,0xffff);

	// initialize the watchdog timer
	status = XWdtTb_Initialize(&WDTInst, WDT_DEVICEID);
	if(status != XST_SUCCESS){
		xil_printf("wdt initialization failed");
		return XST_FAILURE;
	}

	return XST_SUCCESS;
}

/**
 * this is the button handler function. this gets called whenever there is an interrupt raised
 * by the button GPIO, which happens whenever you press a button
 */
void button_handler(void)
{
	// following the basic interrupt handling here
	// disabling the interrupt
	XGpio_InterruptDisable(&BTNInst, 1);
	disable_interrupt(BTN_GPIO_INTR_NUM);

	// once the interrupt has been disabled we can read in the current button state
	btn_state = XGpio_DiscreteRead(&BTNInst,1);
	// using sem post here, we wake up the button thread that has been put to sleep
	// and then that thread can do its functionality.
	sem_post(&btn_press_sema);
	// clearing and acknowledging the interrupt
	XGpio_InterruptClear(&BTNInst, 1);
	acknowledge_interrupt(BTN_GPIO_INTR_NUM);

	// now that we are done dealing with the reason why the interrupt was raised
	// that is reading the button state, we can now enable the interrupt again
	XGpio_InterruptEnable(&BTNInst, 1);
	enable_interrupt(BTN_GPIO_INTR_NUM);
}

/**
 * comment to be added after reading wdt functionality
 */
void wdt_handler(void)
{
	// disable the watchdog timer interrupt
	disable_interrupt(WDT_INTR_NUM);
	// in case the forced crash has not been initiated we have no reason to crash the system
	// hence we restart the watch dog timer
	if(sys_state == 0){
		XWdtTb_RestartWdt(&WDTInst);
	}
	xil_printf("inside wdt handler\r\n");
	// acknowledging and enabling the interrupt
	acknowledge_interrupt(WDT_INTR_NUM);
	enable_interrupt(WDT_INTR_NUM);
}

